﻿// Copyright (c) Microsoft Corporation.  All rights reserved.
// This source code is made available under the terms of the Microsoft Public License (MS-PL)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;

namespace IQToolkit.Data.Firebird
{
    using IQToolkit.Data.Common;

    public class FirebirdFormatter : SioMdmSqlFormatter
        {
            protected FirebirdFormatter(QueryLanguage language)
                : base(language)
            {

            }

            public static new string Format(Expression expression)
            {
                return Format(expression, new FirebirdLanguage());
            }

            public static string FormatFields(Expression expression, ref Dictionary<string, Object> parameters, List<Sio.Mdm.Linq.TableAlias> aliases)
            {
                var formatter = new FirebirdFormatter(new FirebirdLanguage());
                var result = formatter.BuildFormatFields(expression, ref parameters, aliases);
                return result;
            }

            public static string MakeWhere(Expression expression, out Dictionary<string, Object> parameters, out Dictionary<string, Sio.Mdm.Entities.EntityFactory.EntityInfo> aliases)
            {
                var formatter = new FirebirdFormatter(new FirebirdLanguage());
                var result = formatter.FormatWhere(expression, out parameters, out aliases);
                return result;
            }

            public static string Format(Expression expression, QueryLanguage language)
            {
                FirebirdFormatter formatter = new FirebirdFormatter(language);
                formatter.Visit(expression);
                return formatter.ToString();
            }

            protected override void WriteAggregateName(string aggregateName)
            {
                if (aggregateName == "LongCount")
                {
                    this.Write("COUNT_BIG");
                }
                else
                {
                    base.WriteAggregateName(aggregateName);
                }
            }

                protected virtual bool IsString(Type type)
        {
            return type == typeof(string);
        }




            protected override Expression VisitMemberAccess(MemberExpression m)
            {
                if (m.Member.DeclaringType == typeof(string))
                {
                    switch (m.Member.Name)
                    {
                        case "Length":
                            this.Write("LEN(");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                    }
                }
                else if (m.Member.DeclaringType == typeof(DateTime) || m.Member.DeclaringType == typeof(DateTimeOffset))
                {
                    switch (m.Member.Name)
                    {
                        case "Day":
                            this.Write("DAY(");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                        case "Month":
                            this.Write("MONTH(");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                        case "Year":
                            this.Write("YEAR(");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                        case "Hour":
                            this.Write("DATEPART(hour, ");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                        case "Minute":
                            this.Write("DATEPART(minute, ");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                        case "Second":
                            this.Write("DATEPART(second, ");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                        case "Millisecond":
                            this.Write("DATEPART(millisecond, ");
                            this.Visit(m.Expression);
                            this.Write(")");
                            return m;
                        case "DayOfWeek":
                            this.Write("(DATEPART(weekday, ");
                            this.Visit(m.Expression);
                            this.Write(") - 1)");
                            return m;
                        case "DayOfYear":
                            this.Write("(DATEPART(dayofyear, ");
                            this.Visit(m.Expression);
                            this.Write(") - 1)");
                            return m;
                    }
                }
                return base.VisitMemberAccess(m);
            }

            protected override string GetOperator(BinaryExpression b)
            {
                if ((b.NodeType == ExpressionType.Add) && (b.Method.ReturnType == typeof(String))
)
                    return "||";
                else
                    if (b.NodeType == ExpressionType.Equal)
                    {
                        return (IsString(b.Left.Type)) ? " like " : "=";
                    }
                    else if (b.NodeType == ExpressionType.NotEqual)
                    {
                        return (IsString(b.Left.Type)) ? " not like " : " <>";
                    }
                    else
                        return base.GetOperator(b);
            }

            protected override Expression VisitMethodCall(MethodCallExpression m)
            {
                if (m.Method.DeclaringType == typeof(string))
                {
                    switch (m.Method.Name)
                    {
                        case "StartsWith":
                            this.Write("(");
                            this.Visit(m.Object);
                            this.Write(" LIKE ");
                            this.Visit(m.Arguments[0]);
                            this.Write(" || '%')");
                            return m;
                        case "EndsWith":
                            this.Write("(");
                            this.Visit(m.Object);
                            this.Write(" LIKE '%' || ");
                            this.Visit(m.Arguments[0]);
                            this.Write(")");
                            return m;
                        case "Contains":
                            this.Write("(");
                            this.Visit(m.Object);
                            this.Write(" LIKE ");
                            this.Visit(m.Arguments[0]);
                            this.Write(" )");
                            return m;
                        case "Concat":
                            IList<Expression> args = m.Arguments;
                            if (args.Count == 1 && args[0].NodeType == ExpressionType.NewArrayInit)
                            {
                                args = ((NewArrayExpression)args[0]).Expressions;
                            }
                            for (int i = 0, n = args.Count; i < n; i++)
                            {
                                if (i > 0) this.Write(" || ");
                                this.Visit(args[i]);
                            }
                            return m;
                        case "IsNullOrEmpty":
                            this.Write("(");
                            this.Visit(m.Arguments[0]);
                            this.Write(" IS NULL OR ");
                            this.Visit(m.Arguments[0]);
                            this.Write(" = '')");
                            return m;
                        case "ToUpper":
                            this.Write("UPPER(");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "ToLower":
                            this.Write("LOWER(");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "Replace":
                            this.Write("REPLACE(");
                            this.Visit(m.Object);
                            this.Write(", ");
                            this.Visit(m.Arguments[0]);
                            this.Write(", ");
                            this.Visit(m.Arguments[1]);
                            this.Write(")");
                            return m;
                        case "Substring":
                            this.Write("SUBSTRING(");
                            this.Visit(m.Object);
                            this.Write(", ");
                            this.Visit(m.Arguments[0]);
                            this.Write(" + 1, ");
                            if (m.Arguments.Count == 2)
                            {
                                this.Visit(m.Arguments[1]);
                            }
                            else
                            {
                                this.Write("8000");
                            }
                            this.Write(")");
                            return m;
                        case "Remove":
                            this.Write("STUFF(");
                            this.Visit(m.Object);
                            this.Write(", ");
                            this.Visit(m.Arguments[0]);
                            this.Write(" + 1, ");
                            if (m.Arguments.Count == 2)
                            {
                                this.Visit(m.Arguments[1]);
                            }
                            else
                            {
                                this.Write("8000");
                            }
                            this.Write(", '')");
                            return m;
                        case "IndexOf":
                            this.Write("(CHARINDEX(");
                            this.Visit(m.Arguments[0]);
                            this.Write(", ");
                            this.Visit(m.Object);
                            if (m.Arguments.Count == 2 && m.Arguments[1].Type == typeof(int))
                            {
                                this.Write(", ");
                                this.Visit(m.Arguments[1]);
                                this.Write(" + 1");
                            }
                            this.Write(") - 1)");
                            return m;
                        case "Trim":
                            this.Write("TRIM(");
                            this.Visit(m.Object);
                            this.Write("))");
                            return m;
                    }
                }
                else if (m.Method.DeclaringType == typeof(DateTime))
                {
                    switch (m.Method.Name)
                    {
                        case "op_Subtract":
                            if (m.Arguments[1].Type == typeof(DateTime))
                            {
                                this.Write("DATEDIFF(");
                                this.Visit(m.Arguments[0]);
                                this.Write(", ");
                                this.Visit(m.Arguments[1]);
                                this.Write(")");
                                return m;
                            }
                            break;
                        case "AddYears":
                            this.Write("DATEADD(YYYY,");
                            this.Visit(m.Arguments[0]);
                            this.Write(",");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "AddMonths":
                            this.Write("DATEADD(MM,");
                            this.Visit(m.Arguments[0]);
                            this.Write(",");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "AddDays":
                            this.Write("DATEADD(DAY,");
                            this.Visit(m.Arguments[0]);
                            this.Write(",");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "AddHours":
                            this.Write("DATEADD(HH,");
                            this.Visit(m.Arguments[0]);
                            this.Write(",");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "AddMinutes":
                            this.Write("DATEADD(MI,");
                            this.Visit(m.Arguments[0]);
                            this.Write(",");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "AddSeconds":
                            this.Write("DATEADD(SS,");
                            this.Visit(m.Arguments[0]);
                            this.Write(",");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                        case "AddMilliseconds":
                            this.Write("DATEADD(MS,");
                            this.Visit(m.Arguments[0]);
                            this.Write(",");
                            this.Visit(m.Object);
                            this.Write(")");
                            return m;
                    }
                }
                else if (m.Method.DeclaringType == typeof(Decimal))
                {
                    switch (m.Method.Name)
                    {
                        case "Add":
                        case "Subtract":
                        case "Multiply":
                        case "Divide":
                        case "Remainder":
                            this.Write("(");
                            this.VisitValue(m.Arguments[0]);
                            this.Write(" ");
                            this.Write(GetOperator(m.Method.Name));
                            this.Write(" ");
                            this.VisitValue(m.Arguments[1]);
                            this.Write(")");
                            return m;
                        case "Negate":
                            this.Write("-");
                            this.Visit(m.Arguments[0]);
                            this.Write("");
                            return m;
                        case "Ceiling":
                        case "Floor":
                            this.Write(m.Method.Name.ToUpper());
                            this.Write("(");
                            this.Visit(m.Arguments[0]);
                            this.Write(")");
                            return m;
                        case "Round":
                            if (m.Arguments.Count == 1)
                            {
                                this.Write("ROUND(");
                                this.Visit(m.Arguments[0]);
                                this.Write(", 0)");
                                return m;
                            }
                            else if (m.Arguments.Count == 2 && m.Arguments[1].Type == typeof(int))
                            {
                                this.Write("ROUND(");
                                this.Visit(m.Arguments[0]);
                                this.Write(", ");
                                this.Visit(m.Arguments[1]);
                                this.Write(")");
                                return m;
                            }
                            break;
                        case "Truncate":
                            this.Write("ROUND(");
                            this.Visit(m.Arguments[0]);
                            this.Write(", 0, 1)");
                            return m;
                    }
                }
                else if (m.Method.DeclaringType == typeof(Math))
                {
                    switch (m.Method.Name)
                    {
                        case "Abs":
                        case "Acos":
                        case "Asin":
                        case "Atan":
                        case "Cos":
                        case "Exp":
                        case "Log10":
                        case "Sin":
                        case "Tan":
                        case "Sqrt":
                        case "Sign":
                        case "Ceiling":
                        case "Floor":
                            this.Write(m.Method.Name.ToUpper());
                            this.Write("(");
                            this.Visit(m.Arguments[0]);
                            this.Write(")");
                            return m;
                        case "Atan2":
                            this.Write("ATN2(");
                            this.Visit(m.Arguments[0]);
                            this.Write(", ");
                            this.Visit(m.Arguments[1]);
                            this.Write(")");
                            return m;
                        case "Log":
                            if (m.Arguments.Count == 1)
                            {
                                goto case "Log10";
                            }
                            break;
                        case "Pow":
                            this.Write("POWER(");
                            this.Visit(m.Arguments[0]);
                            this.Write(", ");
                            this.Visit(m.Arguments[1]);
                            this.Write(")");
                            return m;
                        case "Round":
                            if (m.Arguments.Count == 1)
                            {
                                this.Write("ROUND(");
                                this.Visit(m.Arguments[0]);
                                this.Write(", 0)");
                                return m;
                            }
                            else if (m.Arguments.Count == 2 && m.Arguments[1].Type == typeof(int))
                            {
                                this.Write("ROUND(");
                                this.Visit(m.Arguments[0]);
                                this.Write(", ");
                                this.Visit(m.Arguments[1]);
                                this.Write(")");
                                return m;
                            }
                            break;
                        case "Truncate":
                            this.Write("ROUND(");
                            this.Visit(m.Arguments[0]);
                            this.Write(", 0, 1)");
                            return m;
                    }
                }
                if (m.Method.Name == "ToString")
                {
                    if (m.Object.Type != typeof(string))
                    {
                        this.Write("Cast( ");
                        this.Visit(m.Object);
                        this.Write(" as varchar(50))");
                    }
                    else
                    {
                        this.Visit(m.Object);
                    }
                    return m;
                }
                else if (!m.Method.IsStatic && m.Method.Name == "CompareTo" && m.Method.ReturnType == typeof(int) && m.Arguments.Count == 1)
                {
                    this.Write("(CASE WHEN ");
                    this.Visit(m.Object);
                    this.Write(" = ");
                    this.Visit(m.Arguments[0]);
                    this.Write(" THEN 0 WHEN ");
                    this.Visit(m.Object);
                    this.Write(" < ");
                    this.Visit(m.Arguments[0]);
                    this.Write(" THEN -1 ELSE 1 END)");
                    return m;
                }
                else if (m.Method.IsStatic && m.Method.Name == "Compare" && m.Method.ReturnType == typeof(int) && m.Arguments.Count == 2)
                {
                    this.Write("(CASE WHEN ");
                    this.Visit(m.Arguments[0]);
                    this.Write(" = ");
                    this.Visit(m.Arguments[1]);
                    this.Write(" THEN 0 WHEN ");
                    this.Visit(m.Arguments[0]);
                    this.Write(" < ");
                    this.Visit(m.Arguments[1]);
                    this.Write(" THEN -1 ELSE 1 END)");
                    return m;
                }
                return base.VisitMethodCall(m);
            }

            protected override NewExpression VisitNew(NewExpression nex)
            {
                if (nex.Constructor.DeclaringType == typeof(DateTime))
                {
                    if (nex.Arguments.Count == 3)
                    {
                        this.Write("Convert(DateTime, ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[0]);
                        this.Write(") + '/' + ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[1]);
                        this.Write(") + '/' + ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[2]);
                        this.Write("))");
                        return nex;
                    }
                    else if (nex.Arguments.Count == 6)
                    {
                        this.Write("Convert(DateTime, ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[0]);
                        this.Write(") + '/' + ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[1]);
                        this.Write(") + '/' + ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[2]);
                        this.Write(") + ' ' + ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[3]);
                        this.Write(") + ':' + ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[4]);
                        this.Write(") + ':' + ");
                        this.Write("Convert(nvarchar, ");
                        this.Visit(nex.Arguments[5]);
                        this.Write("))");
                        return nex;
                    }
                }
                return base.VisitNew(nex);
            }

            protected override Expression VisitBinary(BinaryExpression b)
            {
                if (b.NodeType == ExpressionType.Power)
                {
                    this.Write("POWER(");
                    this.VisitValue(b.Left);
                    this.Write(", ");
                    this.VisitValue(b.Right);
                    this.Write(")");
                    return b;
                }
                else if (b.NodeType == ExpressionType.Coalesce)
                {
                    this.Write("COALESCE(");
                    this.VisitValue(b.Left);
                    this.Write(", ");
                    Expression right = b.Right;
                    while (right.NodeType == ExpressionType.Coalesce)
                    {
                        BinaryExpression rb = (BinaryExpression)right;
                        this.VisitValue(rb.Left);
                        this.Write(", ");
                        right = rb.Right;
                    }
                    this.VisitValue(right);
                    this.Write(")");
                    return b;
                }
                else if (b.NodeType == ExpressionType.LeftShift)
                {
                    this.Write("(");
                    this.VisitValue(b.Left);
                    this.Write(" * POWER(2, ");
                    this.VisitValue(b.Right);
                    this.Write("))");
                    return b;
                }
                else if (b.NodeType == ExpressionType.RightShift)
                {
                    this.Write("(");
                    this.VisitValue(b.Left);
                    this.Write(" / POWER(2, ");
                    this.VisitValue(b.Right);
                    this.Write("))");
                    return b;
                }
                return base.VisitBinary(b);
            }

            protected override Expression VisitValue(Expression expr)
            {
                if (IsPredicate(expr))
                {
                    this.Write("CASE WHEN (");
                    this.Visit(expr);
                    this.Write(") THEN 1 ELSE 0 END");
                    return expr;
                }
                return base.VisitValue(expr);
            }

            protected override Expression VisitConditional(ConditionalExpression c)
            {
                if (this.IsPredicate(c.Test))
                {
                    this.Write("(CASE WHEN ");
                    this.VisitPredicate(c.Test);
                    this.Write(" THEN ");
                    this.VisitValue(c.IfTrue);
                    Expression ifFalse = c.IfFalse;
                    while (ifFalse != null && ifFalse.NodeType == ExpressionType.Conditional)
                    {
                        ConditionalExpression fc = (ConditionalExpression)ifFalse;
                        this.Write(" WHEN ");
                        this.VisitPredicate(fc.Test);
                        this.Write(" THEN ");
                        this.VisitValue(fc.IfTrue);
                        ifFalse = fc.IfFalse;
                    }
                    if (ifFalse != null)
                    {
                        this.Write(" ELSE ");
                        this.VisitValue(ifFalse);
                    }
                    this.Write(" END)");
                }
                else
                {
                    this.Write("(CASE ");
                    this.VisitValue(c.Test);
                    this.Write(" WHEN 0 THEN ");
                    this.VisitValue(c.IfFalse);
                    this.Write(" ELSE ");
                    this.VisitValue(c.IfTrue);
                    this.Write(" END)");
                }
                return c;
            }

            protected override Expression VisitRowNumber(RowNumberExpression rowNumber)
            {
                this.Write("ROW_NUMBER() OVER(");
                if (rowNumber.OrderBy != null && rowNumber.OrderBy.Count > 0)
                {
                    this.Write("ORDER BY ");
                    for (int i = 0, n = rowNumber.OrderBy.Count; i < n; i++)
                    {
                        OrderExpression exp = rowNumber.OrderBy[i];
                        if (i > 0)
                        {
                            this.Write(", ");
                        }
                        this.VisitValue(exp.Expression);
                        if (exp.OrderType != OrderType.Ascending)
                        {
                            this.Write(" DESC");
                        }
                    }
                }
                this.Write(")");
                return rowNumber;
            }

            protected override Expression VisitIf(IFCommand ifx)
            {
                if (!this.Language.AllowsMultipleCommands)
                {
                    return base.VisitIf(ifx);
                }
                this.Write("IF ");
                this.Visit(ifx.Check);
                this.WriteLine(Indentation.Same);
                this.Write("BEGIN");
                this.WriteLine(Indentation.Inner);
                this.VisitStatement(ifx.IfTrue);
                this.WriteLine(Indentation.Outer);
                if (ifx.IfFalse != null)
                {
                    this.Write("END ELSE BEGIN");
                    this.WriteLine(Indentation.Inner);
                    this.VisitStatement(ifx.IfFalse);
                    this.WriteLine(Indentation.Outer);
                }
                this.Write("END");
                return ifx;
            }

            protected override Expression VisitBlock(BlockCommand block)
            {
                if (!this.Language.AllowsMultipleCommands)
                {
                    return base.VisitBlock(block);
                }

                for (int i = 0, n = block.Commands.Count; i < n; i++)
                {
                    if (i > 0)
                    {
                        this.WriteLine(Indentation.Same);
                        this.WriteLine(Indentation.Same);
                    }
                    this.VisitStatement(block.Commands[i]);
                }
                return block;
            }

            protected override Expression VisitDeclaration(DeclarationCommand decl)
            {
                if (!this.Language.AllowsMultipleCommands)
                {
                    return base.VisitDeclaration(decl);
                }

                for (int i = 0, n = decl.Variables.Count; i < n; i++)
                {
                    var v = decl.Variables[i];
                    if (i > 0)
                        this.WriteLine(Indentation.Same);
                    this.Write("DECLARE @");
                    this.Write(v.Name);
                    this.Write(" ");
                    this.Write(this.Language.TypeSystem.GetVariableDeclaration(v.QueryType, false));
                }
                if (decl.Source != null)
                {
                    this.WriteLine(Indentation.Same);
                    this.Write("SELECT ");
                    for (int i = 0, n = decl.Variables.Count; i < n; i++)
                    {
                        if (i > 0)
                            this.Write(", ");
                        this.Write("@");
                        this.Write(decl.Variables[i].Name);
                        this.Write(" = ");
                        this.Visit(decl.Source.Columns[i].Expression);
                    }
                    if (decl.Source.From != null)
                    {
                        this.WriteLine(Indentation.Same);
                        this.Write("FROM ");
                        this.VisitSource(decl.Source.From);
                    }
                    if (decl.Source.Where != null)
                    {
                        this.WriteLine(Indentation.Same);
                        this.Write("WHERE ");
                        this.Visit(decl.Source.Where);
                    }
                }
                else
                {
                    for (int i = 0, n = decl.Variables.Count; i < n; i++)
                    {
                        var v = decl.Variables[i];
                        if (v.Expression != null)
                        {
                            this.WriteLine(Indentation.Same);
                            this.Write("SET @");
                            this.Write(v.Name);
                            this.Write(" = ");
                            this.Visit(v.Expression);
                        }
                    }
                }
                return decl;
            }
        }
    

}
